﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using EPiServer;
using EPiServer.Core;
using EPiServer.DataAccess;
using EPiServer.Filters;
using EPiServer.Security;
using PageTypeBuilder;
using TemplateFoundation.Configuration;
using TemplateFoundation.Exceptions;
using TemplateFoundation.PageTypes;
using TemplateFoundation.Reflection;
using TemplateFoundation.Tags;

namespace TemplateFoundation.Core
{
    /// <summary>
    /// Primarily used for common page queries
    /// </summary>
    public class PageFactory
    {
        private static readonly PageFactory _instance = new PageFactory();

        PageFactory()
        {
            
        }

        /// <summary>
        /// Provides access to PageFactory helper methods
        /// </summary>
        public static PageFactory Instance
        {
            get
            {
                return _instance;
            }
        }

        #region Contacts

        /// <summary>
        /// Returns all person pages
        /// </summary>
        /// <returns>If contact person page type has been specified in site settings only pages of that specific page type will be returned</returns>
        public PageDataCollection GetPersons()
        {
            return GetPersons(true);
        }

        /// <summary>
        /// Returns all person pages
        /// </summary>
        /// <returns>If contact person page type has been specified in site settings only pages of that specific page type will be returned</returns>
        public PageDataCollection GetPersons(bool filterForVisitors)
        {
            PageDataCollection pages;

            // Search entire site if no contacts container has been set
            if (PageReference.IsNullOrEmpty(Settings.Instance.ContactsContainer))
            {
                // Verify a page type for contact persons has been specified
                if (Settings.Instance.PersonPageTypeId.HasValue)
                {
                    pages =
                        GetPagesByType(
                            PageTypeResolver.Instance.GetPageTypeType(Settings.Instance.PersonPageTypeId.Value),
                            filterForVisitors);
                }
                else
                {
                    pages = GetPagesByType(
                        PageTypeInheritance.FindPageTypeFamily(typeof (PersonPageBase)),
                        filterForVisitors);
                }

                var persons = new PageDataCollection(pages);

                persons.Sort(new FilterPropertySort("PageName", FilterSortDirection.Ascending));

                return persons;
            }

            // Contacts container set, return all person pages beneath it
            var contacts = GetContacts(filterForVisitors);
          
            return new PageDataCollection(contacts.Where(p => p is PersonPageBase));
        }

        /// <summary>
        /// Returns all contact pages, including contact persons
        /// </summary>
        /// <returns>Pages inheriting from <see cref="ContactPageBase"/></returns>
        public PageDataCollection GetContacts()
        {
            return GetContacts(true);
        }

        /// <summary>
        /// Returns all contact pages, including contact persons
        /// </summary>
        /// <returns>Pages inheriting from <see cref="ContactPageBase"/></returns>
        public PageDataCollection GetContacts(bool filterForVisitors)
        {
            // Search through entire site if no contacts container has been set
            if (PageReference.IsNullOrEmpty(Settings.Instance.ContactsContainer))
            {
                var pageTypes = PageTypeInheritance.FindPageTypeFamily(typeof (ContactPageBase));

                var pages = new PageDataCollection(GetPagesByType(pageTypes, filterForVisitors));

                pages.Sort(new FilterPropertySort("PageName", FilterSortDirection.Ascending));

                return pages;
            }

            // Contacts container set, get all contact pages beneath it
            var contactPageLinks = DataFactory.Instance.GetDescendents(Settings.Instance.ContactsContainer);

            var contactPages = new PageDataCollection();

            foreach (var contactPageLink in contactPageLinks)
            {
                var p = contactPageLink.GetPage();

                if (p is ContactPageBase)
                {
                    contactPages.Add(p);
                }
            }

            contactPages.Sort(new FilterPropertySort("PageName", FilterSortDirection.Ascending));

            return contactPages;
        }

        #endregion

        #region Articles

        /// <summary>
        /// Returns all articles under the root news container
        /// </summary>
        /// <returns></returns>
        public PageDataCollection GetArticles(int maxCount)
        {
            return GetArticles(maxCount, string.Empty);
        }

        /// <summary>
        /// Returns all articles under the root news container
        /// </summary>
        /// <returns></returns>
        public PageDataCollection GetArticles(int maxCount, string languageBranch)
        {
            return GetArticles(maxCount, languageBranch, false);
        }

        /// <summary>
        /// Returns all articles under the root news container
        /// </summary>
        /// <returns></returns>
        public PageDataCollection GetArticles(int maxCount, bool includeNotPublished)
        {
            return GetArticles(maxCount, string.Empty, includeNotPublished);
        }

        /// <summary>
        /// Returns all articles under the root news container
        /// </summary>
        /// <returns></returns>
        public PageDataCollection GetArticles(string languageBranch, bool includeNotPublished)
        {
            return GetArticles(int.MaxValue, languageBranch, includeNotPublished);
        }

        /// <summary>
        /// Returns all articles under the root news container
        /// </summary>
        /// <returns></returns>
        public PageDataCollection GetArticles()
        {
            return GetArticles(string.Empty);
        }

        /// <summary>
        /// Returns all articles under the root news container
        /// </summary>
        /// <param name="languageBranch"></param>
        /// <returns></returns>
        public PageDataCollection GetArticles(string languageBranch)
        {
            return GetArticles(languageBranch, false);
        }

        /// <summary>
        /// Returns all articles under the root news container
        /// </summary>
        /// <param name="includeNotPublished">Set to true to include articles that aren't published</param>
        /// <param name="languageBranch"></param>
        /// <param name="maxCount"></param>
        /// <returns></returns>
        public PageDataCollection GetArticles(int maxCount, string languageBranch, bool includeNotPublished)
        {
            if (PageReference.IsNullOrEmpty(Settings.Instance.NewsContainer))
            {
                if (PageReference.StartPage==PageReference.RootPage)
                {
                    throw new TemplateFoundationException("Unable to retrieve articles because the site's start page has not been set in configuration");
                }

                throw new TemplateFoundationException("Unable to retrieve articles as the news container setting has not been set");
            }

            if(maxCount==0)
                return new PageDataCollection(0);

            if(string.IsNullOrEmpty(languageBranch))
            {
                languageBranch = DataFactory.Instance.CurrentPage!=null ? 
                                 DataFactory.Instance.CurrentPage.LanguageBranch : 
                                 GetStartPage().MasterLanguageBranch;
            }

            // Initialize page data collection of suitable size
            var articles = maxCount!=int.MaxValue ? new PageDataCollection(maxCount) : new PageDataCollection();

            if (Settings.Instance.DisableDateFolderSorting)
            {
                if(!Settings.Instance.ArticlePageTypeId.HasValue)
                    throw new TemplateFoundationException("Unable to retrieve news articles when date folder sorting is disabled and no article page type has been specified in site settings");

                if (includeNotPublished)
                {
                    var criterias = new PropertyCriteriaCollection
                                        {
                                            new PropertyCriteria
                                                {
                                                    Condition = CompareCondition.Equal,
                                                    Name = "PageTypeID",
                                                    Value = Settings.Instance.ArticlePageTypeId.Value.ToString(),
                                                    Type = PropertyDataType.PageType
                                                }
                                        };

                    articles = DataFactory.Instance.FindAllPagesWithCriteria(Settings.Instance.NewsContainer, criterias,
                                                                              languageBranch,
                                                                              new LanguageSelector(languageBranch));
                }
                else
                {
                    // Get page references for all pages beneath the news container
                    var allPageReferences = DataFactory.Instance.GetDescendents(Settings.Instance.NewsContainer);

                    // Add article pages to collection
                    foreach (var pageReference in allPageReferences)
                    {
                        var page = pageReference.GetPage() as ArticlePageBase;

                        if (page!=null)
                            articles.Add(page);

                        articles = FilterForVisitor.Filter(articles);

                        if (articles.Count>=maxCount)
                            break;
                    }                   
                }
            }
            else // Go through news containers until the required (or all) articles have been retrieved
            {
                // Get all year containers
                var yearContainers = new PageDataCollection(
                                         DataFactory.Instance.GetChildren(Settings.Instance.NewsContainer, new LanguageSelector(languageBranch))
                                         .OfType<ArticleDateListingPageBase>());
                
                // Sort them by publish date to get the latest one first
                yearContainers.Sort(new FilterPropertySort("PageStartPublish", FilterSortDirection.Descending));

                // Loop through the year containers
                foreach (var yearContainer in yearContainers)
                {
                    // Get the month containers
                    var monthContainers = new PageDataCollection(
                                              DataFactory.Instance.GetChildren(yearContainer.PageLink, new LanguageSelector(languageBranch))
                                              .OfType<ArticleListingPageBase>());

                    // Sort them to get the latest one first
                    monthContainers.Sort(new FilterPropertySort("PageStartPublish", FilterSortDirection.Descending));

                    // Loop through the month containers
                    foreach (var monthContainer in monthContainers)
                    {
                        if (includeNotPublished)
                        {
                            articles.Add(DataFactory.Instance.GetChildren(monthContainer.PageLink, LanguageSelector.AutoDetect(true)));
                        }
                        else
                        {
                            articles.Add(DataFactory.Instance.GetChildren(monthContainer.PageLink));    
                        }

                        if (articles.Count>=maxCount)
                            break;
                    }

                    if (articles.Count>=maxCount)
                        break;
                }   
            }

            // Filter unpublished articles if needed
            if (!includeNotPublished)
            {
                articles = FilterForVisitor.Filter(articles);
            }

            // Sort by publishing date
            articles.Sort(new FilterPropertySort("PageStartPublish", FilterSortDirection.Descending));

            // Limit max count if needed
            if (articles.Count>maxCount)
            {
                articles.RemoveRange(maxCount,articles.Count-maxCount);
            }

            return articles;
        }

        /// <summary>
        /// Gets articles for the specified year
        /// </summary>
        /// <param name="year">The year.</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByYear(int year)
        {
            return GetArticlesByYear(year, string.Empty);
        }

        /// <summary>
        /// Gets articles for the specified year and language
        /// </summary>
        /// <param name="year">The year.</param>
        /// <param name="languageBranch">The language branch.</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByYear(int year, string languageBranch)
        {
            // Find year container
            foreach (var yearContainer in DataFactory.Instance.GetChildren(Settings.Instance.NewsContainer))
            {
                if (yearContainer.PageName==year.ToString())
                {
                    var articles = new PageDataCollection();

                    foreach (var monthContainer in DataFactory.Instance.GetChildren(yearContainer.PageLink))
                    {
                        if(!string.IsNullOrEmpty(languageBranch))
                        {
                            articles.Add(DataFactory.Instance.GetChildren(monthContainer.PageLink, new LanguageSelector(languageBranch)));   
                        }
                        else
                        {
                            articles.Add(DataFactory.Instance.GetChildren(monthContainer.PageLink));    
                        }
                    }

                    articles.Sort(new FilterPropertySort("PageStartPublish", FilterSortDirection.Descending));

                    return articles;
                }
            }

            return new PageDataCollection(); // No articles published during requested year
        }

        /// <summary>
        /// Gets articles by month.
        /// </summary>
        /// <param name="year">The year.</param>
        /// <param name="month">The month.</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByMonth(int year, int month)
        {
            return GetArticlesByMonth(year, month, string.Empty);
        }

        /// <summary>
        /// Gets articles by month and language
        /// </summary>
        /// <param name="year">The year.</param>
        /// <param name="month">The month.</param>
        /// <param name="languageBranch">The language branch.</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByMonth(int year, int month, string languageBranch)
        {
            // Find year container
            foreach (var yearContainer in DataFactory.Instance.GetChildren(Settings.Instance.NewsContainer))
            {
                if (yearContainer.PageName==year.ToString())
                {
                    // Find month container
                    foreach (var monthContainer in DataFactory.Instance.GetChildren(yearContainer.PageLink))
                    {
                        if (monthContainer.PageName==month.ToString())
                        {
                            PageDataCollection pages = null;

                            if(!string.IsNullOrEmpty(languageBranch))
                            {
                                pages = DataFactory.Instance.GetChildren(monthContainer.PageLink, new LanguageSelector(languageBranch));    
                            }
                            else
                            {
                                pages = DataFactory.Instance.GetChildren(monthContainer.PageLink);    
                            }

                            pages.Sort(new FilterPropertySort("PageStartPublish", FilterSortDirection.Descending));

                            return pages;
                        }
                    }
                }
            }

            return new PageDataCollection(); // No articles published during requested month
        }

        /// <summary>
        /// Gets articles tagged with any of the specified tags
        /// </summary>
        /// <param name="tags">The tags.</param>
        /// <param name="languageBranch">Language branch if other than current</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByTag(TagCollection tags, string languageBranch)
        {
            var pages = new PageDataCollection();

            foreach (var tag in tags)
            {
                pages.Add(GetArticlesByTag(tag,languageBranch));
            }

            pages.Sort(new FilterPropertySort("PageStartPublish", FilterSortDirection.Descending));

            return pages;
        }

        /// <summary>
        /// Gets articles tagged with any of the specified tags
        /// </summary>
        /// <param name="tags">The tags.</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByTag(TagCollection tags)
        {
            return GetArticlesByTag(tags, string.Empty);
        }

        /// <summary>
        /// Gets articles by tag
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByTag(Tag tag)
        {
            return GetArticlesByTag(tag, string.Empty);
        }

        /// <summary>
        /// Retrieves articles or posts based on the specified tag
        /// </summary>
        /// <param name="tag"></param>
        /// <param name="languageBranch"></param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByTag(Tag tag, string languageBranch)
        {
            // TODO Steps to test:
            // 1) Retrieve a list for a tag and see that the result gets added to the cache
            // 2) Publish another article to ensure the DataFactoryCache.RemoveListing method is called properly and that the cache object is cleared

            var cacheKey = "articles-" + tag.Name + languageBranch;

            var cache = CacheManager.RuntimeCacheGet(cacheKey) as PageDataCollection;

            if(cache==null)
            {
                // Refresh cache
                var articles = from a in GetArticles(languageBranch) where ((PageTypeBase)a).Tags.Contains(tag) orderby a.StartPublish descending select a;

                var pages = new PageDataCollection();

                foreach(var article in articles)
                {
                    pages.Add(article);
                }

                if (!PageReference.IsNullOrEmpty(tag.PageLink))
                {
                    // Additional cache updates are triggered by the CacheManagement event handler when articles are published
                    CacheManager.RuntimeCacheInsert(cacheKey, pages, DataFactoryCache.CreateDependency(tag.PageLink));
                }

                return pages;
            }

            return cache;
        }

        /// <summary>
        /// Retrieves articles by a specific author
        /// </summary>
        /// <param name="author">Link to the contact page of the author to filter by</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByAuthor(PageReference author)
        {
            return GetArticlesByAuthor(author, string.Empty);
        }

        /// <summary>
        /// Retrieves articles by a specific author
        /// </summary>
        /// <param name="author">Link to the contact page of the author to filter by</param>
        /// <param name="languageBranch">Language branch if other than current</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByAuthor(PageReference author, string languageBranch)
        {
            // TODO Do this using FindPagesWithCriteria instead and add caching
            return new PageDataCollection(GetArticles(languageBranch).Cast<PageTypeBase>().Where(a => !PageReference.IsNullOrEmpty(a.PageContact)).Where(a => a.PageContact.ID==author.ID));
        }

        /// <summary>
        /// Retrieves articles with a specific tag by a specific author
        /// </summary>
        /// <param name="tag">Tag to filter by</param>
        /// <param name="author">Link to the contact page of the author to filter by</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByTagAndAuthor(Tag tag, PageReference author)
        {
            return GetArticlesByTagAndAuthor(tag, author, string.Empty);
        }

        /// <summary>
        /// Retrieves articles with a specific tag by a specific author
        /// </summary>
        /// <param name="tag">Tag to filter by</param>
        /// <param name="author">Link to the contact page of the author to filter by</param>
        /// <param name="languageBranch">Language branch if other than current</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByTagAndAuthor(Tag tag, PageReference author, string languageBranch)
        {
            // TODO Add caching
            return new PageDataCollection(GetArticlesByTag(tag, languageBranch).Cast<PageTypeBase>().Where(a => !PageReference.IsNullOrEmpty(a.PageContact)).Where(a => a.PageContact.ID==author.ID));
        }

        /// <summary>
        /// Retrieves articles with specific tags by a specific author
        /// </summary>
        /// <param name="tags">Tags to filter by</param>
        /// <param name="author">Link to the contact page of the author to filter by</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByTagAndAuthor(TagCollection tags, PageReference author)
        {
            return GetArticlesByTagAndAuthor(tags, author, string.Empty);
        }

        /// <summary>
        /// Retrieves articles with specific tags by a specific author
        /// </summary>
        /// <param name="tags">Tags to filter by</param>
        /// <param name="author">Link to the contact page of the author to filter by</param>
        /// <param name="languageBranch">Language branch if other than current</param>
        /// <returns></returns>
        public PageDataCollection GetArticlesByTagAndAuthor(TagCollection tags, PageReference author, string languageBranch)
        {
            // TODO Add caching
            return new PageDataCollection(GetArticlesByTag(tags, languageBranch).Cast<PageTypeBase>().Where(a => a.PageContact==author));
        }

        #endregion

        #region Get pages by type

        /// <summary>
        /// We need a static version of this for use in event handlers
        /// </summary>
        private static PageDataCollection GetPagesByTypeStatic(Type pageType, bool filterForVisitors)
        {
            Debug.WriteLine(string.Format("Retrieving pages of type {0}, with filtering for visitors {1}", pageType.Name, filterForVisitors ? "enabled" : "disabled"), "[ETF] PageFactory");

            var cacheKey = PageFactoryCache.GetPagesByTypeCacheKey(pageType, filterForVisitors);

            var cache = CacheManager.RuntimeCacheGet(cacheKey) as PageDataCollection;

            if (cache==null)
            {
                Debug.WriteLine("Refreshing cache for page type " + pageType.Name, "[ETF]");

                int? pageTypeId = PageTypeResolver.Instance.GetPageTypeID(pageType);

                var criterias = new PropertyCriteriaCollection();

                if (pageTypeId.HasValue) // Verify page type ID could be resolved
                {
                    criterias.Add(new PropertyCriteria // Search for the specified page type
                    {
                        Condition = CompareCondition.Equal,
                        Name = "PageTypeID",
                        Type = PropertyDataType.PageType,
                        Value = pageTypeId.Value.ToString(),
                        Required = true
                    });

                    criterias.Add(new PropertyCriteria // We always exclude pages in the recycle bin
                    {
                        Condition = CompareCondition.NotEqual,
                        Name = "PageParentLink",
                        Type = PropertyDataType.PageReference,
                        Value = PageReference.WasteBasket.ID.ToString(),
                        Required = true
                    });
                }

                if (!criterias.Any())
                {
                    return new PageDataCollection(); // Return empty page collection if no page type could be resolved
                }

                var result = DataFactory.Instance.FindPagesWithCriteria(
                    PageReference.RootPage,
                    criterias);

                if (!result.Any())
                {
                    return result;
                }

                if (filterForVisitors)
                {
                    result = FilterForVisitor.Filter(result);
                }

                result.Sort(new FilterPropertySort("StartPublish", FilterSortDirection.Descending));

                // HACK Explicit PTB casting required because of CMS 6 R2 issue: http://pagetypebuilder.codeplex.com/workitem/8171
                if (!(result.First() is PageTypeBase))
                {
                    for (int i = 0; i < result.Count; i++)
                    {
                        result[i] = PageTypeResolver.Instance.ConvertToTyped(result[i]);
                    }
                }

                CacheManager.RuntimeCacheAdd(
                    cacheKey,
                    result);

                return result;
            }

            return cache;
        }

        /// <summary>
        /// Returns all publicly available pages of the specified type
        /// </summary>
        /// <returns></returns>
        /// <remarks>Note that this method searches the entire site, from the root page</remarks>
        public PageDataCollection GetPagesByType(Type pageType, bool filterForVisitors)
        {
            return GetPagesByTypeStatic(pageType, filterForVisitors);
        }

        /// <summary>
        /// Returns all publicly available pages of the specified type
        /// </summary>
        /// <returns></returns>
        /// <remarks>Note that this method searches the entire site, from the root page</remarks>
        public PageDataCollection GetPagesByType(int pageTypeId, bool filterForVisitors)
        {
            return GetPagesByTypeStatic(PageTypeResolver.Instance.GetPageTypeType(pageTypeId), filterForVisitors);
        }

        /// <summary>
        /// Returns all publicly available pages of the specified types
        /// </summary>
        /// <returns></returns>
        /// <remarks>Note that this method searches the entire site, from the root page</remarks>
        public PageDataCollection GetPagesByType(IEnumerable<Type> pageTypes, bool filterForVisitors)
        {
            var pages = new PageDataCollection();
        
            foreach(var type in pageTypes)
            {
                pages.Add(GetPagesByType(type, filterForVisitors));
            }

            return pages;
        }

        /// <summary>
        /// Returns all pages that have a MainBody property (ie inheriting from StandardPageBase)
        /// </summary>
        /// <param name="filterForVisitor">Set to true to use the FilterForVisitor filter before returning the result</param>
        /// <param name="maxCount">Maximum number of pages to return, 0 returns all</param>
        /// <returns></returns>
        public PageDataCollection GetStandardPages(int maxCount, bool filterForVisitor)
        {
            var types = PageTypeInheritance.FindStandardPageTypes();

            var pages = GetPagesByType(types, filterForVisitor);

            int pageCount = pages.Count;

            if(maxCount>0 && maxCount < pageCount)
            {
                pages.RemoveRange(maxCount,pageCount-maxCount);
            }

            return pages;
        }

        /// <summary>
        /// Gets the start page
        /// </summary>
        /// <returns></returns>
        /// <remarks>If the start page doesn't inherit StartPageBase an exception will be thrown</remarks>
        public StartPageBase GetStartPage()
        {
            return GetStartPage<StartPageBase>();
        }

        /// <summary>
        /// Gets the start page cast to the specified type
        /// </summary>
        /// <returns></returns>
        /// <remarks>If the start page doesn't inherit from the specified type an exception will be thrown</remarks>
        public T GetStartPage<T>() where T : StartPageBase
        {
            // Return null if no start page reference is available of if it's set to the root page
            if (PageReference.IsNullOrEmpty(PageReference.StartPage) || PageReference.StartPage==PageReference.RootPage)
            {
                return null;
            }

            return DataFactory.Instance.GetPage(PageReference.StartPage) as T;
        }

        #endregion

        #region Globalization

        /// <summary>
        /// Ensures a page exists for all language branches
        /// </summary>
        /// <param name="page"></param>
        /// <remarks>Additional language branches will get the same page name and URL segment</remarks>
        public void CreateLanguageBranches(PageData page)
        {
            // Create the container for all languages available (determined by the start page)
            foreach (var language in GetStartPage().PageLanguages)
            {
                CreateLanguageBranch(page, language);
            }
        }

        /// <summary>
        /// Creates and publishes a language version (if it doesn't yet exist) for a page
        /// </summary>
        /// <param name="page"></param>
        /// <param name="languageBranch"></param>
        public void CreateLanguageBranch(PageData page, string languageBranch)
        {
            CreateLanguageBranch(page,languageBranch,false);
        }

        /// <summary>
        /// Creates a language version (if it doesn't yet exist) for a page
        /// </summary>
        /// <param name="page"></param>
        /// <param name="languageBranch"></param>
        /// <param name="doNotPublish">If the language version should be saved but not published</param>
        public void CreateLanguageBranch(PageData page, string languageBranch, bool doNotPublish)
        {
            if (string.IsNullOrEmpty(page.PageName) && !doNotPublish)
            {
                throw new NotSupportedException("To publish a new language version for a page the original page must have a name, but the 'PageName' property was empty");
            }

            // Check if language already exists
            bool languageExists = false;

            foreach (var p in DataFactory.Instance.GetLanguageBranches(page.PageLink))
            {
                if (p.LanguageBranch==languageBranch)
                {
                    languageExists = true;
                    break;
                }
            }

            if (!languageExists)
            {
                var languageVersion = DataFactory.Instance.CreateLanguageBranch(page.PageLink, new LanguageSelector(languageBranch));
                
                if(!doNotPublish) // To publish a page it must have a name
                {
                    languageVersion.PageName = page.PageName;
                    languageVersion.URLSegment = page.URLSegment;
                }

                try
                {
                    DataFactory.Instance.Save(languageVersion, doNotPublish ? SaveAction.Save : SaveAction.Publish, AccessLevel.NoAccess);
                }
                catch (SqlException ex)
                {
                    throw new TemplateFoundationException("Unable to create a language version for page with ID " + page.PageLink.ID + ". Perhaps the master language isn't published?", ex);
                }
            }
        }

        /// <summary>
        /// Checks if a page exists for a specific language branch
        /// </summary>
        /// <param name="pageLink">The page to check</param>
        /// <param name="languageBranch">The language branch to look for</param>
        /// <param name="filterForVisitors">Set to true to only include language versions that are published and available to site visitors</param>
        /// <returns>Returns true if the page exists for the specified language</returns>
        public bool LanguageVersionExists(PageReference pageLink, string languageBranch, bool filterForVisitors)
        {
            var page = DataFactory.Instance
                        .GetLanguageBranches(PageReference.StartPage)
                        .FirstOrDefault(p => p.LanguageBranch == languageBranch);

            return filterForVisitors ? page.IsPublished() : page != null;
        }

        /// <summary>
        /// Returns all published language versions for a page
        /// </summary>
        /// <param name="pageLink"></param>
        /// <returns></returns>
        public PageDataCollection GetPublishedLanguageBranches(PageReference pageLink)
        {
            var languageBranches = DataFactory.Instance.GetLanguageBranches(pageLink);

            return FilterForVisitor.Filter(languageBranches);
        }

        #endregion
    }
}
